﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Imaginecup.recycling.Object;


namespace Imaginecup.Recycling.Object
{
    [Flags]
    public enum Direction
    {
        None = 0,

        Left = 1,
        Right = 2,

        Up = 4,
        Down = 8,

        LeftUp = Left | Up,
        LeftDown = Left | Down,

        RightUp = Right | Up,
        RightDown = Right | Down,
    }
    class BackgroundTexture : IGameObject
    {
        protected IManager m_Manager;

        public void SetManager(IManager manager)
        {
            m_Manager = manager;
        }

        private int PrimaryNumber;
        
        // 텍스처
        private Texture2D texture;

        // 텍스처에서 그려질 범위
        private Rectangle sourceRange;

        // 실제 그려질 크기(크기에 따라 확대/축소)
        private Rectangle destinationRange;

        // 실제 그려질 영역(크기에 따라 타일링)
        private Rectangle displayRange;

        // 텍스처가 그려질 시작점
        private Point startPosition;

        // 움직일 방향
        private Point scrollDirection;

        // 움직일 속도
        private Point scrollSpeed;

        // 화면안 반복
        private Point repeat;

        // 가로 비율
        private float horizontalScale;
        // 세로 비율
        private float verticalScale;

        // 방향(열거형)
   

        // 스크롤 방향을 표시, 설정
        public Direction ScrollDirection
        {
            get
            {
                // 현재 설정된 스크롤 방향을 반환
                return PointToDirection(scrollDirection);
            }
            set
            {
                //전달받은 방향을 X, Y 좌표값으로 표시 변경
                scrollDirection = DirectionToPoint(value);
            }
        }


        //텍스처를 표시, 설정(설정 필수)
        public Texture2D Texture
        {
            get
            {
                // 현재 설정되어 있는 텍스처를 반환
                return texture;
            }
            set
            {
                // 전달받은 텍스처를 저장
                texture = value;

                sourceRange = new Rectangle(0, 0, texture.Width, texture.Height);

            }
        }

        //잘라내기 영역을 표시, 설정
        //(텍스처에서 출력에 사용할 부분)
        public Rectangle ClippingRange
        {
            get
            {
                // 현재 설정된 잘라내기 영역을 반환
                return sourceRange;
            }
            set
            {
                // 전달받은 영역을 저장
                sourceRange = value;

                // 잘라내기 영역과 대상 출력 영역이 일치하도록 설정
                TargetRange = value;
            }
        }

        public Rectangle TargetRange
        {
            get
            {
                // 현재 설정된 대상 출력 영역을 반환
                return destinationRange;
            }
            set
            {
                // 전달받은 영역을 저장
                destinationRange = value;

                TargetRange = sourceRange;

                HorizontalScale = (float)destinationRange.Width / sourceRange.Width;
                VerticalScale = (float)destinationRange.Height / sourceRange.Height;


            }
        }

        public Rectangle DisplayRange
        {
            get
            {
                // 현재 설정된 출력 영역을 반환
                return displayRange;
            }
            set
            {
                // 전달받은 영역을 저장
                displayRange = value;

                // 텍스처의 출력 시작 위치를 출력 영역의 좌상단으로 설정
                startPosition.X = displayRange.X;
                startPosition.Y = displayRange.Y;

                // 대상 출력 영역과 출력 영역이 일치하도록 반복 횟수 설정
                Repeat = 1;
            }
        }

        // 스크롤 속도를 표시, 설정
        public int Speed
        {
            get
            {
                // 가로, 세로 방향 스크롤 속도 중 느린 속도를 반환
                return Math.Min(scrollSpeed.X, scrollSpeed.Y);
            }
            set
            {
                // 전달받은 값으로 가로, 세로 방향 스크롤 속도 설정
                HorizontalSpeed = VerticalSpeed = value;
            }
        }

        // 수평축( 가로 방향 ) 이동 속도를 표시, 설정
        public int HorizontalSpeed
        {
            get
            {
                // 현재 설정된 가로 방향 스크롤 속도 반환
                return scrollSpeed.X;
            }
            set
            {
                // 전달받은 값이 양수를 갖도록 변환,
                // 변환된 값을 가로 방향 스크롤 속도로 설정
                scrollSpeed.X = Math.Abs(value);
            }
        }

        // 수직축( 세로 방향 ) 이동 속도를 표시, 설정
        public int VerticalSpeed
        {
            get
            {
                // 현재 설정된 세로 방향 스크롤 속도를 반환
                return scrollSpeed.Y;
            }
            set
            {
                // 전달받은 값을 양수로 변환,
                // 변환된 값을 세로 방향 스크롤 속도로 설정
                scrollSpeed.Y = Math.Abs(value);
            }
        }

        // 대상 출력 영역의 출력 영역 반복 횟수를 표시, 설정
        // ( 설정시 대상 출력 영역, 확대 축소 비율 자동 변경 )
        // 반복 횟수가 1 일 경우 대상 출력 영역과 출력 영역의 크기는 같음
        public int Repeat
        {
            get
            {
                // 가로, 세로 방향 반복 횟수 중 작은 값을 반환
                return Math.Min(HorizontalRepeat, VerticalRepeat);
            }
            set
            {
                // 전달받은 반복 횟수로 가로, 세로 방향 반복 횟수 설정
                HorizontalRepeat = VerticalRepeat = value;
            }
        }

        // 가로 방향 반복 횟수를 표시, 설정
        // ( 설정시 대상 출력 영역, 확대 축소 비율 자동 변경 )
        public int HorizontalRepeat
        {
            get
            {
                // 현재 설정된 가로 방향 반복 횟수를 반환
                // ( 타일링 효과를 위해 증가시킨 1을 감소 )
                return repeat.X - 1;
            }
            set
            {
                // 전달받은 값이 0인 경우 예외 처리
                // ( 나눗셈에서 에러가 발생하지 않도록 )
                if (0 == value)
                {
                    // 설정 가능한 최소값으로 변경
                    value = 1;
                }

                // 전달받은 반복 횟수를 양수로 변환,
                // 변환된 값으로 가로 방향 반복 횟수를 설정
                repeat.X = Math.Abs(value);

                // 가로 방향 반복 횟수로 대상 출력 영역의 가로 길이 설정
                destinationRange.Width = displayRange.Width / repeat.X;

                // 가로 방향 확대 축소 비율을 계산, 설정
                horizontalScale = (float)destinationRange.Width / sourceRange.Width;

                // 텍스처가 이어지도록 반복 횟수를 1 증가
                repeat.X = repeat.X + 1;
            }
        }

        // 세로 방향 반복 횟수를 표시, 설정
        // ( 설정시 대상 출력 영역, 확대 축소 비율 자동 변경 )
        public int VerticalRepeat
        {
            get
            {
                // 현재 설정된 세로 방향 반복 횟수를 반환
                // ( 타일링 효과를 위해 증가시킨 1을 감소 )
                return repeat.Y - 1;
            }
            set
            {
                // 전달받은 값이 0인 경우 예외 처리
                // ( 나눗셈에서 에러가 발생하지 않도록 )
                if (0 == value)
                {
                    // 설정 가능한 최소값으로 변경
                    value = 1;
                }

                // 전달받은 반복 횟수를 양수로 변환,
                // 변환된 값으로 세로 방향 반복 횟수를 설정
                repeat.Y = Math.Abs(value);

                // 세로 방향 반복 횟수로 대상 출력 영역의 세로 길이 설정
                destinationRange.Height = displayRange.Height / repeat.Y;

                // 세로 방향 확대 축소 비율을 계산, 설정
                verticalScale = (float)destinationRange.Height / sourceRange.Height;

                // 텍스처가 이어지도록 반복 횟수를 1 증가
                repeat.Y = repeat.Y + 1;
            }
        }



        // 잘라내기 영역에 대한 대상 영역의 크기 비율
        // 비율이 1.0 보다 작은 경우, 잘라내기 영역이 대상 출력 영역보다 큰 경우(축소)
        // 비율이 1.0 보다 큰 경우, 잘라내기 영역이 대상 출력 영역보다 작은 경우(확대)
        // 비율이 1.0 인 경우, 잘라내기 영역과 대상 출력 영역이 같음
        // 잘라내기 영역의 확대 축소 비율을 표시, 설정
        // ( 설정시 대상 출력 영역, 반복 횟수 자동 변경 )        
        public float Scale
        {
            get
            {
                // 가로, 세로 방향 확대 축소 비율 중 작은 값 반환
                return Math.Min(HorizontalScale, VerticalScale);
            }
            set
            {
                // 전달 받은 값으로 가로, 세로 방향 확대 축소 비율을 설정
                HorizontalScale = VerticalScale = value;
            }
        }


        // 잘라내기 영역의 가로 방향 확대 축소 비율을 표시, 설정
        // ( 설정시 대상 출력 영역, 반복 횟수 자동 변경 )
        public float HorizontalScale
        {
            get
            {
                // 현재 설정된 가로 방향 확대 축소 비율 반환
                return horizontalScale;
            }
            set
            {
                // 전달받은 값이 0인 경우 예외 처리                
                if (0.0f == value)
                {
                    // 기본 값으로 변경
                    value = 1.0f;
                }

                // 전달받은 값을 양수로 변환,
                // 변환된 값으로 가로 방향 확대 축소 비율에 설정
                horizontalScale = Math.Abs(value);

                // 대상 출력 영역의 가로 길이를 설정
                destinationRange.Width = (int)(sourceRange.Width * horizontalScale);

                // 가로 방향 반복 횟수를 계산
                // 나머지 값이 존재하는 경우 반복 횟수 1 증가, 텍스처가 이어지도록 반복 횟수 1 증가
                repeat.X = (int)Math.Ceiling((double)displayRange.Width / destinationRange.Width) + 1;
            }
        }


        // 잘라내기 영역의 세로 방향 확대 축소 비율을 표시, 설정
        // ( 설정시 대상 출력 영역, 반복 횟수 자동 변경 )
        public float VerticalScale
        {
            get
            {
                // 현재 설정된 세로 방향 확대 축소 비율 반환
                return verticalScale;
            }
            set
            {
                // 전달받은 값이 0인 경우 예외 처리                
                if (0.0f == value)
                {
                    // 기본 값으로 변경
                    value = 1.0f;
                }

                // 전달받은 값을 양수로 변환,
                // 변환된 값으로 세로 방향 확대 축소 비율에 설정
                verticalScale = Math.Abs(value);

                // 대상 출력 영역의 세로 길이를 설정
                destinationRange.Height = (int)(sourceRange.Height * verticalScale);

                // 세로 방향 반복 횟수를 계산
                // 나머지 값이 존재하는 경우 반복 횟수 1 증가, 텍스처가 이어지도록 반복 횟수 1 증가
                repeat.Y = (int)Math.Ceiling((double)displayRange.Height / destinationRange.Height) + 1;
            }
        }

        // 
        /// 생성자
        /// 
        public BackgroundTexture()
        {
            InitializeInformation();
        }

        void InitializeInformation() // 기본 정보 초기화
        {
            // 정보를 저장할 공간을 생성, 초기화
            sourceRange = new Rectangle();
            destinationRange = new Rectangle();
            displayRange = new Rectangle();

            // 텍스처 출력 위치 기본값 설정(화면 좌표계의 좌상단, 원점)
            startPosition = Point.Zero;
            // 스크롤 방향 기본값 설정(우하단 방향)
            scrollDirection = new Point(1, 1);
            // 스크롤 속도 기본값 설정(1픽셀, 60분의 1초 기준)
            scrollSpeed = new Point(1, 1);

            // 대상 출력 영역의 출력 영역 반복 횟수 기본 값 설정
            repeat = new Point(1, 1);

            // 확대 축소 비율 기본값 설정
            horizontalScale = verticalScale = 1.0f;
        }


        // 스크롤 방향을 X, Y좌표값으로 표현
        Point DirectionToPoint(Direction direction)
        {
            // 스크롤 방향을 좌표값으로 표시하기 위한 변수 선언
            Point result = Point.Zero;

            // 좌, 우측 이동 여부를 확인
            if ((Direction.Left & direction) != 0)
            {
                // 좌측 이동의 경우

                // 음의 X축 방향으로 이동을 표시 
                result.X = -1;
            }
            else if ((Direction.Right & direction) != 0)
            {
                // 우측 이동의 경우

                // 양의 X축 방향으로 이동을 표시
                result.X = 1;
            }

            // 상, 하측 이동 여부를 확인
            if ((Direction.Up & direction) != 0)
            {
                // 상측 이동의 경우

                // 음의 Y축 방향으로 이동을 표시
                result.Y = -1;
            }
            else if ((Direction.Down & direction) != 0)
            {
                // 하측 이동의 경우

                // 양의 Y축 방향으로 이동을 표시
                result.Y = 1;
            }

            // 좌표값을 반환
            return result;
        }


        // X, Y 좌표값을 스크롤 방향으로 표현
        private Direction PointToDirection(Point direction)
        {
            // 좌표값을 스크롤 방향으로 표시하기 위한 변수 선언, 초기화
            Direction result = Direction.None;

            // 좌, 우측 이동 여부를 확인
            if (-1 == direction.X)
            {
                // 좌측 이동의 경우

                // 좌측 이동을 추가
                result = result | Direction.Left;
            }
            else if (1 == direction.X)
            {
                // 우측 이동의 경우

                // 우측 이동을 추가
                result = result | Direction.Right;
            }

            // 상, 하측 이동 여부를 확인
            if (-1 == direction.Y)
            {
                // 상측 이동의 경우

                // 상측 이동을 추가
                result = result | Direction.Up;
            }
            else if (1 == direction.Y)
            {
                // 하측 이동의 경우

                // 하측 이동을 추가
                result = result | Direction.Down;
            }

            // 스크롤 방향을 반환
            return result;
        }


        // 텍스처를 화면에 출력
        public void Draw(SpriteBatch spriteBatch, GameTime gametime)
        {
            // 반복 출력시 위치를 표시하기 위한 변수 선언, 초기화
            Point repeatPosition = startPosition;

            // 대상 영역 세로 반복 횟수만큼 수행
            for (int row = 0; row != repeat.Y; ++row)
            {
                // 반복 출력시 위치를 계산, 설정
                repeatPosition.Y = startPosition.Y + destinationRange.Height * row;

                // 반복 출력 위치가 출력 영역을 벗어나는지 확인
                if (repeatPosition.Y >= displayRange.Bottom)
                {
                    // 벗어나는 경우( 마지막에 한번만 실행 )

                    // 출력 시작 위치의 반대편에 위치하도록
                    // ( 순환 처리, 출력 시작 위치를 기준으로 반전 )
                    repeatPosition.Y = startPosition.Y - destinationRange.Height;
                }

                // 대상 영역 가로 반복 횟수만큼 수행
                for (int column = 0; column != repeat.X; ++column)
                {
                    // 반복 출력시 위치를 계산, 설정
                    repeatPosition.X = startPosition.X + destinationRange.Width * column;

                    // 반복 출력 위치가 출력 화면을 벗어나는지 확인
                    if (repeatPosition.X >= displayRange.Right)
                    {
                        // 벗어나는 경우( 마지막에 한번만 실행 )

                        // 출력 시작 위치의 반대편에 위치하도록
                        // ( 순환 처리, 출력 시작 위치를 기준으로 반전 )
                        repeatPosition.X = startPosition.X - destinationRange.Width;
                    }

                    // 반복 출력 위치를 사용하여 화면 출력 영역을 이동
                    destinationRange.X = repeatPosition.X;
                    destinationRange.Y = repeatPosition.Y;

                    // 전체 출력 영역과 대상 출력 영역의 교차 영역을 계산, 설정
                    Rectangle intersectionRange = Rectangle.Intersect(displayRange, destinationRange);


                    // 잘라내기 영역에서의 교차 영역이 차지하는 영역을 계산
                    Rectangle matchRange = CalculateClippingRange();

                    // 배경 화면 출력
                    spriteBatch.Begin();
                    spriteBatch.Draw(texture, intersectionRange, matchRange, Color.White);
                    spriteBatch.End();
                }
            }
        }

        // 텍스처 스크롤 정보를 업데이트
        public void Update(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            // 텍스처의 출력 시작 위치를 스크롤의 방향과 속도를 사용하여 계산, 설정
            startPosition.X = (int)(startPosition.X + scrollDirection.X * scrollSpeed.X * elapsed);
            startPosition.Y = (int)(startPosition.Y + scrollDirection.Y * scrollSpeed.Y * elapsed);

            // 출력 시작 위치가 출력 영역의 시작 지점과
            // 출력 영역의 시작 지점에서 대상 출력 영역만큼 증가한 지점 사이에 위치하도록
            while (startPosition.X < displayRange.Left)
            {
                startPosition.X = startPosition.X + destinationRange.Width;
            }

            while (startPosition.Y < displayRange.Top)
            {
                startPosition.Y = startPosition.Y + destinationRange.Height;
            }

            while (startPosition.X > displayRange.Left + destinationRange.Width)
            {
                startPosition.X = startPosition.X - destinationRange.Width;
            }

            while (startPosition.Y > displayRange.Top + destinationRange.Height)
            {
                startPosition.Y = startPosition.Y - destinationRange.Height;
            }
        }


        // 전체 출력 영역과 대상 출력 영역의 교차 부분에 해당하는 텍스처의 잘라내기 영역을 계산
        private Rectangle CalculateClippingRange()
        {
            // 계산 결과를 저장하기 위한 변수 선언
            Rectangle result = new Rectangle();

            // 교차 영역의 X축 시작 위치를 계산 설정
            // 출력 영역의 좌측과 대상 영역의 좌측 사이의 거리를 계산
            // 사이 거리 값이 음수인 경우 잘라내기 영역의 시작 위치와 일치( 대상 영역의 좌측이 그려지는 경우 )
            // 사이 거리 값이 양수인 경우 확대 축소 비율을 적용한 사이 거리 값 만큼 우측으로 이동( 증가 )
            result.X = sourceRange.X + (int)(Math.Max(0, displayRange.X - destinationRange.X) / horizontalScale);

            // 교차 영역의 Y축 시작 위치를 계산 설정
            // 출력 영역의 상측과 대상 영역의 하측 사이의 거리를 계산
            // 사이 거리 값이 음수인 경우 잘라내기 영역의 시작 위치와 일치( 대상 영역의 상측이 그려지는 경우 )
            // 사이 거리 값이 양수인 경우 확대 축소 비율을 적용한 사이 거리 값 만큼 하측으로 이동( 증가 )
            result.Y = sourceRange.Y + (int)(Math.Max(0, displayRange.Y - destinationRange.Y) / verticalScale);

            // 교차 영역의 가로 길이를 계산, 설정( 대상 영역이 출력 영역보다 큰 경우를 감안 )
            // 출력 영역의 우측과 대상 영역의 우측 중 작은 값으로 교차 영역의 우측 값을 계산
            // 출력 영역의 좌측과 대상 영역의 좌측 중 큰 값으로 교차 영역의 좌측 값을 계산
            // 교차 영역의 우측과 좌측 사이의 거리를 계산하고, 확대 축소 비율을 적용
            result.Width = (int)((Math.Min(displayRange.Right, destinationRange.Right)
            - Math.Max(displayRange.Left, destinationRange.Left)) / horizontalScale);

            // 교차 영역의 세로 길이를 계산, 설정( 대상 영역이 출력 영역보다 큰 경우를 감안 )
            // 출력 영역의 하측과 대상 영역의 하측 중 작은 값으로 교차 영역의 하측 값을 계산
            // 출력 영역의 상측과 대상 영역의 상측 중 큰 값으로 교차 영역의 상측 값을 계산
            // 교차 영역의 하측과 상측 사이의 거리를 계산하고, 확대 축소 비율을 적용
            result.Height = (int)((Math.Min(displayRange.Bottom, destinationRange.Bottom)
            - Math.Max(displayRange.Top, destinationRange.Top)) / verticalScale);

            // 계산 결과를 반환
            return result;
        }

        public void SetPrimaryNumber(int number)
        {
            PrimaryNumber = number;
        }
    }
}
